﻿using System;
using System.Collections;
using System.Collections.Generic;
using Boardgame;


namespace BoardgameModelViewed
{
    [FlagsAttribute]
    public enum GameStatus
    {
        Go = 0, Temp = 1, Setup = 2,
        Check = 4, NoMoves = 8, Win = 16, Tie = 32, Loss = 64,
        Checkmate = 4 + 8 + 64, Stalemate = 8 + 32,
        Resign = Loss + 128, NoCaptures = Tie + 256, Repetitive = Tie + 512
    }


    public enum Token { Empty, King, Queen, Rook, Bishop, Knight, Pawn }


    public delegate void GameCreatedEventHandler ();
    public delegate void StatusUpdateEventHandler ();
    public delegate void TokenRemoveEventHandler (int cellX, int cellY);
    public delegate void TokenPlaceEventHandler (int cellX, int cellY, Player player, Token token);
    public delegate void MoveProcessEventHandler ();
    public delegate void UndoPreviewEventHandler ();
    public delegate void HiliteChangeEventHandler (int cellX, int cellY, bool isHilite);
    public delegate void HilitesClearEventHandler ();


    public interface IChessView
    {
        void ShowMessage (string message, Player toPlayer);
        bool ShowQuestion (string message, Player toPlayer);
    }


    /// <summary>
    /// Represent the readonly base of a chess game model.
    /// </summary>
    /// <remarks>
    /// This class provides readonly access to the game (including event notifications).
    /// This class is intended to be visible to the View of the MVC pattern.
    /// </remarks>
    public class ChessModelBase
    {
        public static readonly Cell MinSize = new Cell (6, 6);
        public static readonly Cell MaxSize = new Cell (26, 254);

        protected bool isThreefoldRepDrawRequested;
        internal int kingFile;
        internal string startState;
        internal GameStatus startStatus;
        protected IChessView view;

        public event GameCreatedEventHandler GameCreated;
        public event StatusUpdateEventHandler StatusUpdate;
        public event TokenRemoveEventHandler TokenRemove;
        public event TokenPlaceEventHandler TokenPlace;
        public event MoveProcessEventHandler MoveProcess;
        public event UndoPreviewEventHandler UndoPreview;
        public event HiliteChangeEventHandler HiliteChange;
        public event HilitesClearEventHandler HilitesClear;

        #region Public properties

        public Board Board
        { get; protected set; }

        /// <summary>
        /// Returns <b>true</b> if moving player may successfully request a draw based on the
        /// fifty-move rule; else returns false.
        /// </summary>
        public bool IsFiftyMoveRuleClaimable
        { get; protected set; }

        /// <summary>
        /// Get or set the current player's request a post-move draw based on the threefold
        /// repetition rule.
        /// </summary>
        public bool IsThreefoldRepDrawOnMoveRequested
        { get { return isThreefoldRepDrawRequested; } }

        /// <summary>
        /// Returns <b>true</b> if moving player may successfully request a draw based on the
        /// threefold repetition rule; else returns false.
        /// </summary>
        public bool IsThreefoldRepRuleClaimable
        { get; internal set; }

        public PieceLists Pieces;
        public PlyStack Plies;

        public Type[] Promotions
        { get; internal set; }

        public bool IsUnderway
        {
            get { return (Status & (GameStatus.Win | GameStatus.Tie | GameStatus.Loss | GameStatus.Setup)) == 0; }
        }

        public string CurrentPlayerPromotionName
        {
            get { return Promotions[(int) PlayerTurn].Name; }
        }

        public Player PlayerTurn
        {
            get { return (Player) (Plies.Count % 2); }
        }

        public GameStatus Status
        {
            get { return Plies.Count > 0 ? Plies.Top.PlyStatus : startStatus; }
            protected set
            {
                if (Plies.Count == 0)
                {
                    this.startStatus = value;
                    startState = CalcState();
                }
                else
                {
                    Plies.Top.PlyStatus = value;
                    Plies.Top.state = CalcState();
                }
            }
        }

        #endregion

        #region Constructors

        protected ChessModelBase ()
        { }

        #endregion

        #region Event dispatchers

        public void OnGameCreated (ChessModelBase modelbase)
        { if (GameCreated != null) GameCreated(); }

        public void OnStatusUpdate ()
        { if (StatusUpdate != null) StatusUpdate(); }

        public void OnTokenPlace (int x, int y, Player player, Token token)
        { if (TokenPlace != null) TokenPlace (x, y, player, token); }

        public void OnTokenRemove (int x, int y)
        { if (TokenRemove != null) TokenRemove (x, y); }

        public void OnPieceMoved ()
        { if (MoveProcess != null) MoveProcess(); }

        public void OnUndoPreview ()
        { if (UndoPreview != null) UndoPreview(); }

        public void OnHiliteChange (int x, int y, bool newHilite)
        { if (HiliteChange != null) HiliteChange (x, y, newHilite); }

        public void OnHilitesClear ()
        { if (HilitesClear != null) HilitesClear(); }

        #endregion

        #region Public instance methods

        public IEnumerable GetBoardPieces (Player player)
        {
            // No foreach here because promotions modify collection.
            for (int k = 0; k < Pieces[player].Count; ++k)
            {
                Piece piece = Pieces[player][k];
                if (!piece.IsCaptured)
                    yield return piece;
            }
        }


        public string[] GetStatusText ()
        {
            ChessModelBase game = this;
            string TurnText, Status1 = "", Status2 = "";

            GameStatus newStatus = game.Status;

            if (game.IsUnderway)
                TurnText = game.PlayerTurn.ToString();
            else if ((newStatus & GameStatus.Setup) != 0)
                TurnText = "";
            else
                TurnText = "GAME OVER";

            if (newStatus == GameStatus.Check)
                Status1 = "Check!";
            else if ((newStatus & GameStatus.Win) != 0)
                Status1 = game.PlayerTurn.ToString () + " Wins!";
            else if ((newStatus & GameStatus.Loss) != 0)
            {
                if (newStatus == GameStatus.Checkmate)
                    Status1 = "Checkmate";
                else if ((newStatus & GameStatus.Resign) == GameStatus.Resign)
                    Status1 = "Resignation";
                Status2 = PlayerName (Opponent (PlayerTurn)) + " Wins!";
            }
            else if ((newStatus & GameStatus.Tie) != 0)
            {
                Status1 = "Draw";
                if (newStatus == GameStatus.Stalemate)
                    Status2 = "Stalemate";
                else if (newStatus == GameStatus.NoCaptures)
                    Status2 = "Fifty-move rule";
                else if (newStatus == GameStatus.Repetitive)
                    Status2 = "3fold repetition";
            }
            else
                Status1 = "Go";

            string[] retVal = new string[3] { TurnText, Status1, Status2 };

            return retVal;
        }


        public bool IsMovable (int cellX, int cellY)
        { return Board[cellX, cellY].IsPlayer (PlayerTurn); }


        /// <summary>
        /// One-time assignment of View component of MVC pattern.
        /// </summary>
        /// <param name="view">UI implementation of a View.</param>
        public void SetView (IChessView view)
        {
            if (this.view != null)
                throw new InvalidOperationException ("View already assigned");
            this.view = view;
        }

        #endregion

        #region Public static methods

        public static Player Opponent (Player player)
        {
            return (Player) (1 - player);
        }


        public static string PlayerName (Player player)
        {
            return player == Player.White? "White" : "Black";
        }

        #endregion

        #region Non-public instance methods

        protected bool CalcIsFiftyMoveRuleClaimable ()
        {
            int repeat = 100;

            if (Plies.Count < repeat)
                return false;

            for (int k = Plies.Count - 1; k >= Plies.Count - repeat; --k)
                if (Plies[k].Captured != null || Plies[k].Moving is Pawn || Plies[k].Promotion != null)
                    return false;

            return true;
        }


        protected string CalcState ()
        {
            string result = Board.ToString();

            foreach (var homeRank in new int[] { 0, Board.SizeY-1 })
            {
                var king = Board[kingFile, homeRank] as King;
                if (king != null && king.NeverMoved)
                    foreach (var rookFile in new int[] { 0, Board.SizeX-1 })
                    {
                        var rook = Board[rookFile, homeRank] as Rook;
                        if (rook != null && rook.NeverMoved)
                            result += "C" + homeRank + rookFile;
                    }
            }

            if (Plies.Count > 0)
            {
                Ply ply = Plies.Top;
                var pawn = ply.Moving as Pawn;
                if (pawn != null)
                    foreach (Piece oppPiece in Pieces[ChessModelBase.Opponent (pawn.Side)])
                    {
                        var oppPawn = oppPiece as Pawn;
                        if (oppPawn != null)
                            foreach (Cell move in oppPawn.GetMoves())
                                if (oppPawn.At.X != move.X && Board[move.X, move.Y] == Square.Empty)
                                {
                                    ply.state += "P" + move.X + move.Y;
                                    break;
                                }
                    }
            }

            return result;
        }


        protected GameStatus CalcStatus ()
        {
            bool threatened = IsKingThreatened (PlayerTurn);

            foreach (Piece piece in GetBoardPieces (PlayerTurn))
                foreach (Cell place in piece.GetLegalMoves())
                    if (threatened)
                        return GameStatus.Check;
                    else
                        return GameStatus.Go;

            if (threatened)
                return GameStatus.Checkmate;
            else
                return GameStatus.Stalemate;
        }


        internal bool IsKingThreatened (Player kingPlayer)
        {
            foreach (Piece piece in GetBoardPieces (ChessModelBase.Opponent (kingPlayer)))
                foreach (Cell move in piece.GetMoves())
                    if (Board[move.X, move.Y] is King)
                        return true;

            return false;
        }

        #endregion
    }
}
